﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using Microsoft.Internal;

namespace System.ComponentModel.Composition.Caching.AttributedModel
{
    internal static class ReflectionCacheServices
    {
        public static void WriteParameter(this IDictionary<string, object> cache, ParameterInfo parameter)
        {
            Assumes.NotNull(cache, parameter);

            cache.WriteValue(AttributedCacheServices.CacheKeys.ParameterPosition, parameter.Position);
        }

        public static void WriteMember(this IDictionary<string, object> cache, MemberInfo member)
        {
            Assumes.NotNull(cache, member);

            cache.WriteValue(AttributedCacheServices.CacheKeys.MetadataToken, member.MetadataToken);

            Assembly assembly = member.Module.Assembly;
            if (member.Module != assembly.ManifestModule)
            {   
                // Only store the module in the case, 
                // if it is not the default
                cache.WriteModule(member.Module);
            }

            CacheAssembly(assembly, cache);
        }

        private static void WriteModule(this IDictionary<string, object> cache, Module module)
        {
            Assumes.NotNull(module, cache);

            cache.WriteValue<int>(AttributedCacheServices.CacheKeys.ModuleMetadataToken, module.MetadataToken);
        }

        public static void CacheAssembly(this Assembly assembly, IDictionary<string, object> cache)
        {
            Assumes.NotNull(assembly);
            Assumes.NotNull(cache);

#if !SILVERLIGHT
            // NOTE : we need to store the path to the assembly. We can't use Location as in some cases - such as shadow copying - 
            // a new location will be created each time the application run. The Codebase is significantly more resilient, but 
            // it has the Uri format, and we will need to extract the path. We will also check that the assembly has been loaded
            // from file, as we don't support anything else at this point.
            // To do that we check that 
            // 1. Uri is a file
            // 2. Location is not empty (it being empty indicates that the assembly has been loaded from a byte stream or in some other funky way.
            Uri assemblyCodeBaseUri = new Uri(assembly.CodeBase);
            if (!assemblyCodeBaseUri.IsFile || string.IsNullOrEmpty(assembly.Location))
            {
                throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Strings.AssemblyInformationCannotBeCached, assembly.FullName));
            }
            // Local path properly will give full UNC path
            cache.WriteValue(AttributedCacheServices.CacheKeys.AssemblyLocation, assemblyCodeBaseUri.LocalPath);
            cache.WriteValue(AttributedCacheServices.CacheKeys.AssemblyTimeStamp, File.GetLastWriteTimeUtc(assemblyCodeBaseUri.LocalPath).Ticks);
#endif

            cache.WriteValue(AttributedCacheServices.CacheKeys.Assembly, assembly.FullName);
        }

        public static bool IsAssemblyCacheUpToDate(this IDictionary<string, object> cache)
        {
            Assumes.NotNull(cache);
#if !SILVERLIGHT
            // NOTE : we load assembly with the full name AND codebase
            // This will force the loader to look for the default probing path - local directories, GAC, etc - and only after that fallback onto the path
            // if we want the reverse behavior, we will need to try loading from codebase first, and of that fails load from the defualt context
            string assemblyLocation = cache.ReadValue<string>(AttributedCacheServices.CacheKeys.AssemblyLocation);
            long assemblyTimeStamp = cache.ReadValue<long>(AttributedCacheServices.CacheKeys.AssemblyTimeStamp);
            if (!File.Exists(assemblyLocation))
            {
                return false;
            }
            if (File.GetLastWriteTimeUtc(assemblyLocation).Ticks != assemblyTimeStamp)
            {
                return false;
            }
#endif
            return true;
        }

        public static Assembly LoadAssemblyFromCache(this IDictionary<string, object> cache)
        {
            Assumes.NotNull(cache);

            string assemblyName = cache.ReadValue<string>(AttributedCacheServices.CacheKeys.Assembly);
            string codeBase = cache.ReadValue<string>(AttributedCacheServices.CacheKeys.AssemblyLocation);

            return ReflectionResolver.ResolveAssembly(assemblyName, codeBase);
        }

        private static Module LoadModule(this IDictionary<string, object> cache, Assembly assembly)
        {
            Assumes.NotNull(cache, assembly);

            int metadataToken = cache.ReadValue<int>(AttributedCacheServices.CacheKeys.ModuleMetadataToken);
            if (metadataToken == 0)
            {
                return assembly.ManifestModule;
            }

            return ReflectionResolver.ResolveModule(assembly, metadataToken);
        }

        public static MemberInfo LoadMember(this IDictionary<string, object> cache)
        {
            Assumes.NotNull(cache);

            int metadataToken = cache.ReadValue<int>(AttributedCacheServices.CacheKeys.MetadataToken);
            if (metadataToken == 0)
            {
                return null;
            }

            Assembly assembly = cache.LoadAssemblyFromCache();
            Module module = cache.LoadModule(assembly);

            return ReflectionResolver.ResolveMember(module, metadataToken);
        }

        public static ParameterInfo LoadParameter(this IDictionary<string, object> cache, ConstructorInfo constructor)
        {
            Assumes.NotNull(constructor);
            
            int parameterPosition = cache.ReadValue<int>(AttributedCacheServices.CacheKeys.ParameterPosition);

            ParameterInfo[] parameters = constructor.GetParameters();

            Assumes.IsTrue(parameterPosition >= 0 && parameterPosition < parameters.Length);

            return parameters[parameterPosition];
        }
    }
}